/*******************************************************************************
 ** Name: ddate.cpp                                                             **
 ** Description:                                                              **
 **                                                                           **
 ** Open Source Initiative (OSI) Approved 2-clause BSD License                **
 **                                                                           **
 **  Copyright (c) 2009-2011 B "Gian" James <bjames@government-sanctioned.us> **
 **  All rights reserved.                                                     **
 **                                                                           **
 **  Redistribution and use in source and binary forms, with or without       **
 **  modification, are permitted provided that the following conditions are   **
 **  met:                                                                     **
 **                                                                           **
 **  * Redistributions of source code must retain the above copyright notice, **
 **    this list of conditions and the following disclaimer.                  **
 **  * Redistributions in binary form must reproduce the above copyright      **
 **    notice, this list of conditions and the following disclaimer in the    **
 **    documentation and/or other materials provided with the distribution.   **
 **                                                                           **
 **  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS      **
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED **
 ** TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A           **
 ** PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT        **
 ** HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,    **
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED  **
 ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR    **
 ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    **
 ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING      **
 ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS        **
 ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.              **
 **                                                                           **
 ******************************************************************************/
// Created on March 17, 2010, 8:21 AM
// Modified $Date$ by $Author$
#define ddate_cpp_ID   "$Id$"

extern "C" {
#include <time.h>
}
#include "ddate.hpp"

//////////////////////////////////////[ DiscordianDate
const char
DiscordianDate::oldFormat[] = "Today is %{%A, the %e day of %B%} in the YOLD %Y%N%nCelebrate %H";

const char
DiscordianDate::newFormat[] = "%{%A, %B %d%}, %Y YOLD";

const char *
DiscordianDate::dayLong[] = {
	"Sweetmorn", "Boomtime", "Pungenday", "Prickle-Prickle", "Setting Orange"
};

const char *
DiscordianDate::dayShort[] = {
		"SM","BT","PD","PP","SO"
};

const char *
DiscordianDate::seasonLong[] = {
		"Chaos", "Discord", "Confusion", "Bureaucracy", "The Aftermath"
};

const char *
DiscordianDate::seasonShort[] = {
		"Chs", "Dsc", "Cfn", "Bcy", "Afm"
};

const char *
DiscordianDate::holyDay[5][2] = {
	    { "Mungday", "Chaoflux" },
	    { "Mojoday", "Discoflux" },
	    { "Syaday",  "Confuflux" },
	    { "Zaraday", "Bureflux" },
	    { "Maladay", "Afflux" }

};

const char *
DiscordianDate::excl[] = {
	    "Hail Eris!", "All Hail Discordia!", "Kallisti!", "Fnord.", "Or not.",
	    "Wibble.", "Pzat!", "P'tang!", "Frink!",
	    "Slack!", "Praise \"Bob\"!", "Or kill me.",
	    "Grudnuk demand sustenance!", "Keep the Lasagna flying!",
	    "Umlaut Zebra �ber alles!", "You are what you see.",
	    "Or is it?", "This statement is false.",
	    "Hail Eris, Hack Linux!",
	    ""
};



DiscordianDate::DiscordianDate(const char * fmt)
: Module("ddate")
{
	srandom(time(NULL));
	char * p; fnord = 0;
	tm * eris;
	int bob,raw;

	time_t t = time(NULL);
	eris = localtime(&t);
	bob = eris->tm_yday;
	raw = eris->tm_year;
	hastur = convert(bob,raw);
	fnord = fnord ? fnord:oldFormat;

	format(schwa, fnord, hastur);
}

DiscordianDate::DiscordianDate(const char * fmt, int d, int m, int y)
: Module("ddate")
{
	int moe = d, larry = m, curly = y;
	fnord = ++fmt;
	hastur = makeday(larry,moe,curly);
	if (hastur.season == d - 1) {
		// TODO Invalid date range
		return;
	}
	fnord = fnord ? fnord : newFormat;

	format(schwa, fnord, hastur);
}

void
DiscordianDate::format(char *buf, const char* fmt, struct DiscTime dt)
{
    int tib_start=-1, tib_end=0;
    int i, fmtlen=strlen(fmt);
    char *bufptr=buf;

  /* first, find extents of St. Tib's Day area, if defined */
    for(i=0; i<fmtlen; i++) {
      if(fmt[i]=='%') {
          switch(fmt[i+1]) {
          case 'A':
          case 'a':
          case 'd':
          case 'e':
            if(tib_start>0)       tib_end=i+1;
            else            tib_start=i;
            break;
          case '{': tib_start=i; break;
          case '}': tib_end=i+1; break;
          }
      }
    }

    /* now do the formatting */
    buf[0]=0;

    for(i=0; i<fmtlen; i++) {
      if((i==tib_start) && (dt.day==-1)) {
          /* handle St. Tib's Day */
          strcpy(bufptr, ("St. Tib's Day")); bufptr += 13;
          i=tib_end;
      } else {
          if(fmt[i]=='%') {
            char *wibble=0, snarf[23];
            switch(fmt[++i]) {
            case 'A': wibble=(char*)dayLong[dt.yday%5]; break;
            case 'a': wibble=(char*)dayShort[dt.yday%5]; break;
            case 'B': wibble=(char*)seasonLong[dt.season]; break;
            case 'b': wibble=(char*)seasonShort[dt.season]; break;
            case 'd': sprintf(snarf, "%d", dt.day+1); wibble=snarf; break;
            case 'e': sprintf(snarf, "%d%s", dt.day+1, ending(dt.day+1));
                wibble=snarf; break;
            case 'H': if(dt.day==4||dt.day==49)
                wibble=holyday[dt.season][dt.day==49]; break;
            case 'N': if(dt.day!=4&&dt.day!=49) goto eschaton; break;
            case 'n': *(bufptr++)='\n'; break;
            case 't': *(bufptr++)='\t'; break;

            case 'Y': sprintf(snarf, "%d", dt.year); wibble=snarf; break;
            case '.': wibble=sel(excl, sizeof(excl)/sizeof(excl[0]));
                break;
            case 'X': sprintf(snarf, "%d",
                          xday_countdown(dt.yday, dt.year));
                          wibble = snarf; break;
            }
            if(wibble) {
/*              fprintf(stderr, "wibble = (%s)\n", wibble);*/
                strcpy(bufptr, wibble); bufptr+=strlen(wibble);
            }
          } else {
            *(bufptr++) = fmt[i];
          }
      }
    }
  eschaton:
    *(bufptr)=0;
}

struct DiscTime
DiscordianDate::makeday(int imonth,int iday,int iyear) /*i for input */
{
    struct DiscTime funkychickens;

    int cal[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
    int dayspast=0;

    memset(&funkychickens,0,sizeof(funkychickens));
    /* basic range checks */
    if (imonth < 1 || imonth > 12) {
          funkychickens.season = -1;
          return funkychickens;
    }
    if (iday < 1 || iday > cal[imonth-1]) {
          if (!(imonth == 2 && iday == 29 && iyear%4 == 0 &&
              (iyear%100 != 0 || iyear%400 == 0))) {
                funkychickens.season = -1;
                return funkychickens;
          }
    }

    imonth--;
    funkychickens.year= iyear+1166;
    while(imonth>0) { dayspast+=cal[--imonth]; }
    funkychickens.day=dayspast+iday-1;
    funkychickens.season=0;
    if((funkychickens.year%4)==2) {
      if (funkychickens.day==59 && iday==29)  funkychickens.day=-1;
    }
    funkychickens.yday=funkychickens.day;
/*               note: EQUAL SIGN...hopefully that fixes it */
    while(funkychickens.day>=73) {
      funkychickens.season++;
      funkychickens.day-=73;
    }
    return funkychickens;
}

struct DiscTime
DiscordianDate::convert(int nday, int nyear)
{  struct DiscTime funkychickens;

   funkychickens.year = nyear+3066;
   funkychickens.day=nday;
   funkychickens.season=0;
   if ((funkychickens.year%4)==2)
     {if (funkychickens.day==59)
      funkychickens.day=-1;
     else if (funkychickens.day >59)
       funkychickens.day-=1;
    }
   funkychickens.yday=funkychickens.day;
   while (funkychickens.day>=73)
     { funkychickens.season++;
       funkychickens.day-=73;
     }
   return funkychickens;

 }

int
DiscordianDate::xday_countdown(int yday, int year) {
    int r=(185-yday)+(((yday<59)&&(leapp(year)))?1:0);
    while(year<9827) r+=(leapp(++year)?366:365);
    while(year>9827) r-=(leapp(year--)?366:365);
    return r;
}
